﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Entities;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Domain.Models.Collections;
using AcademicPlanner.Framework.Helpers;
using AcademicPlanner.Framework.Libraries.Validation;
using AcademicPlanner.Domain.Components;

namespace AcademicPlanner.Domain.Models
{
	/// <summary>
	/// Course represents an academic course in the application.
	/// </summary>
	/// <since>1.0</since>
	public class Course : Model
	{
		/// <summary>
		/// Minimum value for the <c>Credits</c> property of a Course: <c>0</c>.
		/// </summary>
		/// <since>1.0</since>
		public static readonly double CREDITS_MIN_VALUE = 0;

		/// <summary>
		/// Minimum value for the <c>Number</c> property of a Course: <c>0</c>.
		/// </summary>
		/// <since>1.0</since>
		public static readonly int NUMBER_MIN_VALUE = 0;

		//public static readonly int NUMBER_MAX_VALUE = 10000;

		/// <summary>
		/// The <c>CourseCategory</c> of this Course.
		/// </summary>
		/// <since>1.0</since>
		protected CourseCategory category;

		/// <summary>
		/// The collection of <c>Course</c>s that are prerequisites to this Course.
		/// </summary>
		/// <since>1.0</since>
		protected CourseCollection prerequisites;

		/// <summary>
		/// The collection of <c>AcademicTerm</c>s that this Course is commonly offered in.
		/// </summary>
		protected AcademicTermCollection commonlyOfferedInTerms;


		/// <summary>
		/// Gets the data layer repository for this Course's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected override EntityRepository EntityRepo { get { return DomainMaster.DSAS.CourseEntityRepo; } }

		/// <summary>
		/// Gets this Course's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected CourseEntity Entity { get { return (CourseEntity)entity; } }

		/// <summary>
		/// Gets this Course's name.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_NAME</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public string Name
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_COURSE, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_NAME);
				return Entity.Name;
			}
		}

		/// <summary>
		/// Gets this Course's number.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_NUMBER</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public int Number
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_COURSE, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_NUMBER);
				return Entity.Number;
			}
		}

		/// <summary>
		/// Gets the credits this Course is worth.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_CREDITS</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public double Credits
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_COURSE, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_COURSE_CREDITS);
				return Entity.Num_Credits;
			}
		}

		/// <summary>
		/// Gets the <c>CourseCategory</c> of this Course.
		/// </summary>
		/// <since>1.0</since>
		public CourseCategory Category
		{
			get
			{
				if (category == null && Entity.CategoryEntity != null)
					category = DomainMaster.ModelsFountain.GetCourseCategory(Entity.CategoryEntity);
				return category;
			}
		}

		/// <summary>
		/// Gets the <c>AcademicPlan</c> this Course belongs to.
		/// </summary>
		/// <since>1.0</since>
		public AcademicPlan Plan
		{
			get
			{
				return Category != null ? category.Division.Plan : null;
			}
		}

		/// <summary>
		/// Gets the collection of <c>Course</c>s that are prerequisites to this Course.
		/// </summary>
		/// <since>1.0</since>
		public CourseCollection Prerequisites
		{
			get
			{
				SetPrerequisites();
				return prerequisites;
			}
		}

		/// <summary>
		/// Gets the collection of <c>AcademicTerm</c>s that this Course is commonly offered in.
		/// </summary>
		/// <since>1.0</since>
		public AcademicTermCollection CommonlyOfferedInTerms
		{
			get
			{
				SetCommonlyOfferedInTerms();
				return commonlyOfferedInTerms;
			}
		}

		/// <summary>
		/// Gets the full name of this Course i.e. its category code, number and name.
		/// </summary>
		/// <since>1.0</since>
		public string FullName { get { return Category.Code + Number.ToString() + " - " + Name; } }

		/// <summary>
		/// Gets the full name of this Course i.e. its category code and number.
		/// </summary>
		/// <since>1.0</since>
		public string ShortName { get { return Category.Code + Number.ToString(); } }


		/// <summary>
		/// Constructs a new Course.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <since>1.0</since>
		public Course()
			: base()
		{
			
		}

		/// <summary>
		/// Constructs a new Course with the provided <c>CourseEntity</c>.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <param name="pEntity">The Course's <c>CourseEntity</c>.</param>
		/// <since>1.0</since>
		public Course(CourseEntity pEntity) 
			: base(pEntity)
		{
			
		}


		private void SetPrerequisites()
		{
			if (prerequisites == null)
				ResetPrerequisites();
		}

		private void ResetPrerequisites()
		{
			prerequisites = new CourseCollection(Entity.PrerequiredCourseEntities);
		}

		private void SetCommonlyOfferedInTerms()
		{
			if (commonlyOfferedInTerms == null)
				ResetCommonlyOfferedInTerms();
		}

		private void ResetCommonlyOfferedInTerms()
		{
			commonlyOfferedInTerms = new AcademicTermCollection(Entity.CommonlyOfferedTermEntities);
		}

		/// <summary>
		/// Validates possible values of a Course's properties.
		/// </summary>
		/// <remarks>
		/// <c>String</c> inputs are sanitized first.
		/// </remarks>
		/// <param name="pName">The Course name.</param>
		/// <param name="pNumber">The Course number.</param>
		/// <param name="pCredits">The number of credits the Course is worth.</param>
		/// <param name="pCategory">The <c>CourseCategory</c> of the Course.</param>
		/// <returns>A Collection of validation errors.</returns>
		/// <seealso cref="M:ValidateName"/>
		/// <seealso cref="M:ValidateNumber"/>
		/// <seealso cref="M:ValidateCredits"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Helpers.SecurityHelper.SanitizeInput"/>
		/// <since>1.0</since>
		public static InvalidModelPropertiesErrorCollection Validate(string pName, int pNumber, double pCredits,
																		CourseCategory pCategory)
		{
			var rVal = new InvalidModelPropertiesErrorCollection("Course");
			InvalidModelPropertyErrorCode vCode;

			if (!(vCode = ValidateName(SecurityHelper.SanitizeInput(pName)))
					.Equals(InvalidModelPropertyErrorCode.NONE))
				rVal.AddOuter("Name", vCode);

			if (!(vCode = ValidateNumber(pNumber)).Equals(InvalidModelPropertyErrorCode.NONE))
				rVal.AddOuter("Number", vCode);
		
			if (!(vCode = ValidateCredits(pCredits)).Equals(InvalidModelPropertyErrorCode.NONE))
				rVal.AddOuter("Credits", vCode);

			if (pCategory == null)
				rVal.AddOuter("Category", InvalidModelPropertyErrorCode.REQUIRED);

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a Course's name.
		/// </summary>
		/// <remarks>
		/// The value must not be blank.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateName(string pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pVal))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of credits a Course is worth.
		/// </summary>
		/// <remarks>
		/// The value must he greater than <see cref="P:CREDITS_MIN_VALUE"/>.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateCredits(double pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (pVal <= CREDITS_MIN_VALUE)
				rVal = InvalidModelPropertyErrorCode.TOO_SMALL;

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a Course's number.
		/// </summary>
		/// <remarks>
		/// The value must he greater than <see cref="P:NUMBER_MIN_VALUE"/>.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateNumber(double pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (pVal <= NUMBER_MIN_VALUE)
				rVal = InvalidModelPropertyErrorCode.TOO_SMALL;
			//else if (pVal > NUMBER_MAX_VALUE)
			//    rVal = InvalidModelPropertyErrorCode.TOO_LARGE;

			return rVal;
		}

		internal void Set(string pName, int pNumber, double pCredits, CourseCategory pCategory)
		{
			var vErrs = Validate(pName, pNumber, pCredits, pCategory);

			if (vErrs.OuterErrors.Count > 0)
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_COURSE_PROPERTIES, vErrs);

			if (Plan != null)
			{
				vErrs = Plan.ValidateCourse(entity.Id, pName ?? "", pNumber);
				if (vErrs.OuterErrors.Count > 0)
					throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_COURSE_PROPERTIES, vErrs);
			}

			Entity.Name = SecurityHelper.SanitizeInput(pName);
			Entity.Num_Credits = pCredits;
			Entity.Number = pNumber;
			Entity.CategoryEntity = (CourseCategoryEntity)GetOtherModelEntity(pCategory);
		}

		/// <summary>
		/// Gets the complete chain of prerequisite <c>Course</c>s for this Course.
		/// </summary>
		/// <remarks>
		/// It is basically the the chain of prerequisites of each prerequisite of
		/// this Course.
		/// </remarks>
		/// <returns>The complete chain of prerequisite <c>Course</c>s</returns>
		/// <since>1.0</since>
		public IList<Course> GetPrerequisitesChain()
		{
			SetPrerequisites();

			var rList = new List<Course>();
			var vList = prerequisites.ToList();

			foreach (var vCrse in vList)
			{
				rList.Add(vCrse);
				rList = new List<Course>(rList.Concat(vCrse.GetPrerequisitesChain()));
			}

			return rList;
		}

		internal void RemovePrerequisite(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (Entity.PrerequiredCourseEntities.Contains(vEnt))
				Entity.PrerequiredCourseEntities.Remove(vEnt);
		}

		internal void AddPrerequisite(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (entity.Id != pCourse.EntityId && !Entity.PrerequiredCourseEntities.Contains(vEnt)
				&& !GetPrerequisitesChain().Contains(pCourse))
				Entity.PrerequiredCourseEntities.Add(vEnt);
		}

		internal void RemoveCommonlyOfferedInTerm(AcademicTerm pTerm)
		{
			var vEnt = (AcademicTermEntity)GetOtherModelEntity(pTerm);

			if (Entity.CommonlyOfferedTermEntities.Contains(vEnt))
				Entity.CommonlyOfferedTermEntities.Remove(vEnt);
		}

		internal void AddCommonlyOfferedInTerm(AcademicTerm pTerm)
		{
			var vEnt = (AcademicTermEntity)GetOtherModelEntity(pTerm);

			if (!Entity.CommonlyOfferedTermEntities.Contains(vEnt))
				Entity.CommonlyOfferedTermEntities.Add(vEnt);
		}
	}
}
